/**************************************************************************************************
 * Filename:       BluetoothManager.java
 * Revised:        $Date: 2013-09-09 16:23:36 +0200 (ma, 09 sep 2013) $
 * Revision:       $Revision: 27674 $
 * <p/>
 * Copyright (c) 2013 - 2014 Texas Instruments Incorporated
 * <p/>
 * All rights reserved not granted herein.
 * Limited License.
 * <p/>
 * Texas Instruments Incorporated grants a world-wide, royalty-free,
 * non-exclusive license under copyrights and patents it now or hereafter
 * owns or controls to make, have made, use, import, offer to sell and sell ("Utilize")
 * this software subject to the terms herein.  With respect to the foregoing patent
 * license, such license is granted  solely to the extent that any such patent is necessary
 * to Utilize the software alone.  The patent license shall not apply to any combinations which
 * include this software, other than combinations with devices manufactured by or for TI (�TI Devices?.
 * No hardware patent is licensed hereunder.
 * <p/>
 * Redistributions must preserve existing copyright notices and reproduce this license (including the
 * above copyright notice and the disclaimer and (if applicable) source code license limitations below)
 * in the documentation and/or other materials provided with the distribution
 * <p/>
 * Redistribution and use in binary form, without modification, are permitted provided that the following
 * conditions are met:
 * <p/>
 * No reverse engineering, decompilation, or disassembly of this software is permitted with respect to any
 * software provided in binary form.
 * any redistribution and use are licensed by TI for use only with TI Devices.
 * Nothing shall obligate TI to provide you with source code for the software licensed and provided to you in object code.
 * <p/>
 * If software source code is provided to you, modification and redistribution of the source code are permitted
 * provided that the following conditions are met:
 * <p/>
 * any redistribution and use of the source code, including any resulting derivative works, are licensed by
 * TI for use only with TI Devices.
 * any redistribution and use of any object code compiled from the source code and any resulting derivative
 * works, are licensed by TI for use only with TI Devices.
 * <p/>
 * Neither the name of Texas Instruments Incorporated nor the names of its suppliers may be used to endorse or
 * promote products derived from this software without specific prior written permission.
 * <p/>
 * DISCLAIMER.
 * <p/>
 * THIS SOFTWARE IS PROVIDED BY TI AND TI�S LICENSORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL TI AND TI�S LICENSORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 **************************************************************************************************/
package com.example.llw.bluetooth;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

// import android.util.Log;

/**
 * Service for managing connection and data communication with a GATT server
 * hosted on a given Bluetooth LE device.
 */
public class BluetoothService extends Service {
    static final String TAG = "BluetoothManager";
    /**
     * gatt action of ble connect
     */
    public final static String ACTION_GATT_CONNECTED = "com.example.ti.ble.common.ACTION_GATT_CONNECTED";
    /**
     * gatt action of ble disconnect
     */
    public final static String ACTION_GATT_DISCONNECTED = "com.example.ti.ble.common.ACTION_GATT_DISCONNECTED";
    /**
     * gatt action of ble discovered
     */
    public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.ti.ble.common.ACTION_GATT_SERVICES_DISCOVERED";
    /**
     * gatt action of ble read callback
     */
    public final static String ACTION_DATA_READ = "com.example.ti.ble.common.ACTION_DATA_READ";
    /**
     * gatt action of ble notify callback
     */
    public final static String ACTION_DATA_NOTIFY = "com.example.ti.ble.common.ACTION_DATA_NOTIFY";
    /**
     * gatt action of ble write callback
     */
    public final static String ACTION_DATA_WRITE = "com.example.ti.ble.common.ACTION_DATA_WRITE";
    /**
     * tag of data
     */
    public final static String EXTRA_DATA = "com.example.ti.ble.common.EXTRA_DATA";
    /**
     * tag of service or attribute 's uuid
     */
    public final static String EXTRA_UUID = "com.example.ti.ble.common.EXTRA_UUID";
    /**
     * tag of message 's status
     */
    public final static String EXTRA_STATUS = "com.example.ti.ble.common.EXTRA_STATUS";
    /**
     * tag of device 's address
     */
    public final static String EXTRA_ADDRESS = "com.example.ti.ble.common.EXTRA_ADDRESS";
    /**
     * Maximum Connection of ble
     */
    public static final int BLUETOOTH_CONNECT_NUMBER = 5;
    // BLE
    private Map<String, BluetoothGatt> gattMap = new HashMap<String, BluetoothGatt>();
    private android.bluetooth.BluetoothManager mBluetoothManager = null;
    private BluetoothAdapter mBtAdapter = null;
    //private BluetoothGatt mBluetoothGatt = null;

    private static BluetoothService mThis = null;

    private volatile boolean mBusy = false; // Write/read pending response

    /**
     * Manage the BLE service
     */
    public class LocalBinder extends Binder {
        public BluetoothService getService() {
            return BluetoothService.this;
        }
    }

    private final IBinder binder = new LocalBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // After using a given device, you should make sure that
        // BluetoothGatt.close() is called
        // such that resources are cleaned up properly. In this particular example,
        // close() is
        // invoked when the UI is disconnected from the Service.
        closeAll();
        return super.onUnbind(intent);
    }

    /**
     * get the gatts of connect device
     *
     * @return gatts
     */
    private Map<String, BluetoothGatt> getGattMap() {
        return gattMap;
    }

    /**
     *Get device connect status
     *
     * @param address device's address
     * @return isConnect
     */
    public boolean isConnect(final String address) {
        BluetoothGatt g = gattMap.get(address);
        if (g == null) {
            return false;
        }
        BluetoothDevice d = g.getDevice();
        if (d == null) {
            return false;
        }
        int connectionState = mBluetoothManager.getConnectionState(d,
                BluetoothProfile.GATT);
        return connectionState == BluetoothProfile.STATE_CONNECTED;
    }

    /**
     * get the device
     *
     * @return device
     */
    public BluetoothDevice getBluetoothDevice(String address) {
        if (address != null) {
            return gattMap.get(address).getDevice();
        }
        return null;
    }

    /**
     * get the gatt of device
     *
     * @return gatt
     */
    private BluetoothGatt getBluetoothGatt(String address) {
        if (address != null) {
            return gattMap.get(address);
        }
        return null;
    }

    private void broadcastUpdate(final String action, final String address,
                                 final int status) {
        final Intent intent = new Intent(action);
        intent.putExtra(EXTRA_ADDRESS, address);
        intent.putExtra(EXTRA_STATUS, status);
        /*for (BluetoothManagerListener ll : mListenerList) {
            ll.onBluetoothManagerListenerCompleted(intent);
        }*/
        sendBroadcast(intent);
        mBusy = false;
    }

    private void broadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic, final int status, final String address) {
        final Intent intent = new Intent(action);
        intent.putExtra(EXTRA_ADDRESS, address);
        intent.putExtra(EXTRA_UUID, characteristic.getUuid().toString());
        intent.putExtra(EXTRA_DATA, characteristic.getValue());
        intent.putExtra(EXTRA_STATUS, status);
       /* for (BluetoothManagerListener ll : mListenerList) {
            ll.onBluetoothManagerListenerCompleted(intent);
        }*/
        sendBroadcast(intent);
        mBusy = false;
    }

    private boolean checkGatt() {
        if (mBtAdapter == null) {
            Log.e(TAG, "BluetoothAdapter not initialized");
            return false;
        }
        /*if (mBluetoothGatt == null) {
            // Log.w(TAG, "BluetoothGatt not initialized");
            return false;
        }*/

        if (mBusy) {
            Log.e(TAG, "manager busy");
            return false;
        }
        return true;

    }

    /**
     * Retrieves a service of supported GATT services on the connected device.
     *
     * @param servUUID Service's uuid
     * @param address  Device's address
     * @return BluetoothGattService of supported service.
     */
    public BluetoothGattService getGattService(UUID servUUID, String address) {
        if (address == null || mThis == null) {
            return null;
        }
        for (BluetoothGattService s : mThis.getSupportedGattServices(address)) {
            if (s.getUuid().equals(servUUID)) {
                return s;
            }
        }
        return null;
    }

    /**
     * list.
     */
    // private ArrayList<BluetoothManagerListener> mListenerList = new ArrayList<BluetoothManagerListener>();

    /**
     * add listener.
     *
     * @param listener listener
     */
    /*public void addListener(BluetoothManagerListener listener) {
        if (isListenerExist(listener)) {
            return;
        }

        mListenerList.add(listener);
    }

    private boolean isListenerExist(BluetoothManagerListener listener) {
        for (BluetoothManagerListener listener1 : mListenerList) {
            if (listener == listener1) {
                return true;
            }
        }
        return false;
    }

    /**
     * remove listener.
     *
     * @param listener listener
     */
    /*public void removeListener(BluetoothManagerListener listener) {
        mListenerList.remove(listener);
    }*/

    /**
     * Initializes a reference to the local Bluetooth adapter.
     *
     * @return Return true if the initialization is successful.
     */
    public boolean initialize() {
        // For API level 18 and above, get a reference to BluetoothAdapter through
        // BluetoothManager.
        mThis = this;
        if (mBluetoothManager == null) {
            mBluetoothManager = (android.bluetooth.BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                // Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        mBtAdapter = mBluetoothManager.getAdapter();
        if (mBtAdapter == null) {
            // Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }
        return true;
    }

    //
    // GATT API
    //

    /**
     * Retrieves the number of GATT services on the connected device. This should
     * be invoked only after {@code BluetoothGatt#discoverServices()} completes
     * successfully.
     *
     * @param address device's address
     * @return A {@code integer} number of supported services.
     */
    public int getNumServices(String address) {

        if (address == null) {
            return 0;
        }
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return 0;
        }

        return mBluetoothGatt.getServices().size();
    }

    /**
     * Retrieves a list of supported GATT services on the connected device. This
     * should be invoked only after {@code BluetoothGatt#discoverServices()}
     * completes successfully.
     *
     * @param address device's address
     * @return A {@code List} of supported services.
     */
    public List<BluetoothGattService> getSupportedGattServices(String address) {
        if (address == null) {
            return null;
        }
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return null;
        }

        return mBluetoothGatt.getServices();
    }

    /**
     * After using a given BLE device, the app must call this method to ensure
     * resources are released properly.
     */
    /*public void close() {
        for (Map.Entry<String, BluetoothGatt> entry : gattMap.entrySet()) {
            BluetoothGatt mBluetoothGatt = entry.getValue();
            if (mBluetoothGatt != null) {
                // Log.i(TAG, "close");
                mBluetoothGatt.close();
            }
        }
        gattMap.clear();
    }
*/

    //
    // Utility functions
    //
   /* public static BluetoothGatt getBtGatt() {
        return mThis.mBluetoothGatt;
    }*/
   /* public static android.bluetooth.BluetoothManager getBtManager() {
        return mThis.mBluetoothManager;
    }*/
    public static BluetoothService getInstance() {
        return mThis;
    }

    public boolean waitIdle(int timeout) {
        timeout /= 10;
        while (--timeout > 0) {
            if (mBusy)
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            else
                break;
        }

        return timeout > 0;
    }
    //-----------------  read and write data ------------------------------

    /**
     * Request a read on a given {@code BluetoothGattCharacteristic}. The read
     * result is reported asynchronously through the
     * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
     * callback.
     *
     * @param address        device's address
     * @param characteristic The characteristic to read from.
     */
    public void readCharacteristic(BluetoothGattCharacteristic characteristic, String address) {
        if (!checkGatt())
            return;
        if (address == null) {
            return;
        }
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return;
        }
        mBusy = true;
        mBluetoothGatt.readCharacteristic(characteristic);
    }

    private ArrayList<BluetoothGattCharacteristic> characteristics;

    private int readCharacteristicsNum = 0;

    /**
     * Request some reads on a given {@code BluetoothGattCharacteristic}. The read
     * result is reported asynchronously through the
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param c       The characteristics to read from.
     * @param address Device's address
     */
    public void readCharacteristics(ArrayList<BluetoothGattCharacteristic> c, String address) {
        BluetoothGatt btGatt = mThis.getBluetoothGatt(address);
        if (btGatt == null) {
            Log.e(TAG, "readCharacteristic mBtGatt is null");
            return;
        }
        characteristics = c;
        doReadCharacteristics(address);
    }

    private void doReadCharacteristics(String address) {
        mThis.readCharacteristic(characteristics.get(readCharacteristicsNum), address);
        readCharacteristicsNum++;
        if (readCharacteristicsNum == characteristics.size()) {
            readCharacteristicsNum = 0;
            characteristics = null;
        }
    }

    /**
     * Writes a given characteristic and its values to the associated remote device.
     * <p>
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param characteristic Characteristic to write on the remote device
     * @param address        Device's address
     * @param b              data
     * @return true, if the write operation was initiated successfully
     */
    public boolean writeCharacteristic(
            BluetoothGattCharacteristic characteristic, byte b, String address) {
        if (!checkGatt())
            return false;
        if (address == null) {
            return false;
        }
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return false;
        }
        byte[] val = new byte[1];
        val[0] = b;
        characteristic.setValue(val);

        mBusy = true;
        return mBluetoothGatt.writeCharacteristic(characteristic);
    }

    /**
     * Writes a given characteristic and its values to the associated remote device.
     * <p>
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param characteristic Characteristic to write on the remote device
     * @param address        Device's address
     * @param b              If true ,Characteristic set value is 1 , if not value is 0
     * @return true, if the write operation was initiated successfully
     */
    public boolean writeCharacteristic(
            BluetoothGattCharacteristic characteristic, boolean b, String address) {
        if (!checkGatt())
            return false;
        if (address == null) {
            return false;
        }
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return false;
        }
        byte[] val = new byte[1];

        val[0] = (byte) (b ? 1 : 0);
        characteristic.setValue(val);
        mBusy = true;
        return mBluetoothGatt.writeCharacteristic(characteristic);
    }

    /**
     * Writes a given characteristic and its values to the associated remote device.
     * <p>
     * {@code BroadcastReceiver#onReceive(Context context, Intent intent)}
     * callback.
     *
     * @param characteristic Characteristic to write on the remote device
     * @param address        Device's address
     * @return true, if the write operation was initiated successfully
     */
    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic, String address) {
        if (!checkGatt())
            return false;
        if (address == null) {
            return false;
        }
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return false;
        }
        mBusy = true;
        return mBluetoothGatt.writeCharacteristic(characteristic);
    }

    //----------------------------- service ---------------------------------

    /**
     * This class is used to hold services and characteristics for subscriptions.
     */
    public static class PropertiesService {
        private UUID service, characteristic;

        public PropertiesService(UUID s, UUID d) {
            service = s;
            characteristic = d;
        }

        public UUID getService() {
            return service;
        }

        public UUID getCharacteristic() {
            return characteristic;
        }
    }

    /**
     * Subscribe to interested Bluetooth Characteristic
     *
     * @param enable  Set to true to enable notifications/indications
     * @param address Device's address
     * @param list    Properties service list
     */
    public void subscribeGattCharacteristics(final boolean enable, final String address, final List<PropertiesService> list) {
        //final boolean enable = f;

        if (list == null || address == null) {
            return;
        }
        BluetoothGatt btGatt = mThis.getGattMap().get(address);
        if (btGatt != null) {
            for (PropertiesService device : list) {
                UUID servUuid = device.getService();
                UUID dataUuid = device.getCharacteristic();
                BluetoothGattService serv = btGatt.getService(servUuid);
                if (serv != null) {
                    BluetoothGattCharacteristic charac = serv.getCharacteristic(dataUuid);
                    if (mThis.setCharacteristicIndication(charac, enable, address)) {
                        //mBtLeService.waitIdle(GATT_TIMEOUT);
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        //setError(mActivity.getString(R.string.Sensor_notification_failed) + " : " + serv.getUuid().toString());
                        break;
                    }
                }
            }
        }
    }

    /**
     * Discovers services offered by a remote device as well as their
     * characteristics and descriptors.
     *
     * @param address device's address
     */
    public void discoverServices(String address) {
        if (address == null) {
            return;
        }
        //mPropertiesServices.clear();
        BluetoothGatt btGatt = gattMap.get(address);
        if (btGatt != null && btGatt.discoverServices()) {
            //mServiceList.clear();
            // setBusy(true);
            Log.e(TAG, "Service discovery started");
        } else {
            Log.e(TAG, "Service discovery start failed");
            //setError(mActivity.getString(R.string.Service_discovery_start_failed));
        }
    }

    /**
     * Enables or disables notification on a give characteristic.
     *
     * @param characteristic Characteristic to act on.
     * @param address        device's address
     * @param enable         Set to true to enable notifications/indications
     * @return true, if the write operation was initiated successfully
     */
    public boolean setCharacteristicNotification(
            BluetoothGattCharacteristic characteristic, boolean enable, String address) {
        if (!checkGatt()) {
            return false;
        }

        if (address == null) {
            return false;
        }
        boolean ok = false;
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return false;
        }
        if (mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) {

            BluetoothGattDescriptor clientConfig = characteristic
                    .getDescriptor(UUID16Attributes.CLIENT_CHARACTERISTIC_CONFIG);
            if (clientConfig != null) {

                if (enable) {
                    // Log.i(TAG, "Enable notification: " +
                    // characteristic.getUuid().toString());
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                } else {
                    // Log.i(TAG, "Disable notification: " +
                    // characteristic.getUuid().toString());
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                }

                if (ok) {
                    mBusy = true;
                    ok = mBluetoothGatt.writeDescriptor(clientConfig);
                    // Log.i(TAG, "writeDescriptor: " +
                    // characteristic.getUuid().toString());
                }
            }
        }

        return ok;
    }

    /**
     * Enables or disables indication on a give characteristic.
     *
     * @param address        device's address
     * @param enable         Set to true to enable notifications/indications
     * @param characteristic Characteristic to act on.
     * @return true, if the write operation was initiated successfully
     */
    public boolean setCharacteristicIndication(
            BluetoothGattCharacteristic characteristic, boolean enable, String address) {
        if (!checkGatt())
            return false;

        if (address == null) {
            return false;
        }
        boolean ok = false;
        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt == null) {
            return false;
        }
        if (mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) {

            BluetoothGattDescriptor clientConfig = characteristic
                    .getDescriptor(UUID16Attributes.CLIENT_CHARACTERISTIC_CONFIG);
            if (clientConfig != null) {

                if (enable) {
                    // Log.i(TAG, "Enable notification: " +
                    // characteristic.getUuid().toString());
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                } else {
                    // Log.i(TAG, "Disable notification: " +
                    // characteristic.getUuid().toString());
                    ok = clientConfig
                            .setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                }

                if (ok) {
                    mBusy = true;
                    ok = mBluetoothGatt.writeDescriptor(clientConfig);
                    // Log.i(TAG, "writeDescriptor: " +
                    // characteristic.getUuid().toString());
                }
            }
        }

        return ok;
    }

    /**
     * enabled value.
     *
     * @param characteristic characteristic
     * @return true, enabled is notification
     */
    public boolean isNotificationEnabled(
            BluetoothGattCharacteristic characteristic) {
        if (!checkGatt())
            return false;

        BluetoothGattDescriptor clientConfig = characteristic
                .getDescriptor(UUID16Attributes.CLIENT_CHARACTERISTIC_CONFIG);
        if (clientConfig == null)
            return false;

        return clientConfig.getValue() == BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
    }

    //-----------------------------connect bluetooth device-----------------------------


    //private boolean isDisconnectAll = false;
    //private int disconnectNum = 0;

    /**
     * Disconnects an existing connection or cancel a pending connection. The
     * disconnection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnect(String address) {
        if (!checkGatt() || address == null) {
            // Log.w(TAG, "disconnect: BluetoothAdapter not initialized");
            return;
        }
        //isDisconnectAll = false;
        final BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
        int connectionState = mBluetoothManager.getConnectionState(device,
                BluetoothProfile.GATT);

        BluetoothGatt mBluetoothGatt = gattMap.get(address);
        if (mBluetoothGatt != null) {
            if (connectionState != BluetoothProfile.STATE_DISCONNECTED) {
                Log.e(TAG, " dis connect address : " + address);
                mBusy = true;
                mBluetoothGatt.disconnect();
            } else {
                // Log.w(TAG, "Attempt to disconnect in state: " + connectionState);
            }
        }
    }

    /**
     * Disconnects all existing connection or cancel a pending connection. The
     * disconnection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnectAll() {
        if (!checkGatt()) {
            // Log.w(TAG, "disconnect: BluetoothAdapter not initialized");
            return;
        }
        if (gattMap.size() == 0) {
            return;
        }
        //isDisconnectAll = true;
        //disconnectNum = 0;
        for (Map.Entry<String, BluetoothGatt> entry : gattMap.entrySet()) {
            //disconnectNum++;
            BluetoothGatt mBluetoothGatt = entry.getValue();
            String address = entry.getKey();
            final BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
            int connectionState = mBluetoothManager.getConnectionState(device,
                    BluetoothProfile.GATT);
            if (mBluetoothGatt != null) {
                if (connectionState != BluetoothProfile.STATE_DISCONNECTED) {
                    mBusy = true;
                    mBluetoothGatt.disconnect();
                } else {
                    // Log.w(TAG, "Attempt to disconnect in state: " + connectionState);
                }
            }
        }
    }

    /**
     * Disconnects all existing connection or cancel a pending connection. The
     * disconnection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void closeAll() {
        if (!checkGatt()) {
            // Log.w(TAG, "disconnect: BluetoothAdapter not initialized");
            return;
        }
        if (gattMap.size() == 0) {
            return;
        }
        int num = 0;
        for (Map.Entry<String, BluetoothGatt> entry : gattMap.entrySet()) {
            num++;
            BluetoothGatt mBluetoothGatt = entry.getValue();
            if (mBluetoothGatt != null) {
                mBluetoothGatt.close();
            }
        }
        if (gattMap.size() == num) {
            gattMap.clear();
        }
    }

    public void close(String address) {
        if (address == null) {
            return;
        }
        BluetoothGatt btGatt = gattMap.get(address);
        if (btGatt != null) {
            // Log.i(TAG, "close");
            btGatt.close();
            //gattMap.remove(entry.getKey());
        }
        gattMap.remove(address);
    }

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * @param address The device address of the destination device.
     * @return Return true if the connection is initiated successfully. The
     * connection result is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public boolean connect(final Context context, final String address, boolean autoConnect) {
        if (!checkGatt() || address == null) {
            Log.e(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }
        final BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
        int connectionState = mBluetoothManager.getConnectionState(device,
                BluetoothProfile.GATT);
        if (device == null) {
            Log.e(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        if (connectionState == BluetoothProfile.STATE_DISCONNECTED) {

            // Previously connected device. Try to reconnect.
            /*if (mBluetoothDeviceAddress != null
                    && address.equals(mBluetoothDeviceAddress)) {
                // Log.d(TAG, "Re-use GATT connection");
                if (mBluetoothGatt.connect()) {
                    return true;
                } else {
                    // Log.w(TAG, "GATT re-connect failed.");
                    return false;
                }
            }*/
            BluetoothGatt g = gattMap.get(address);
            if (g != null) {
                // Previously connected device. Try to reconnect.
                g.connect();
                return true;
            } else {
                // We want to directly connect to the device, so we are setting the
                // autoConnect parameter to false.
                // Log.d(TAG, "Create a new GATT connection.");
                if (gattMap.size() > BLUETOOTH_CONNECT_NUMBER) {
                    Log.e(TAG, "gatt connect exceed 5");
                    return false;
                }
                mBusy = true;
                BluetoothGatt gatt = device.connectGatt(context, autoConnect, mGattCallbacks);
                if (gatt != null) {
                    gattMap.put(address, gatt);
                }
                return true;
            }
            //mBluetoothDeviceAddress = address;
        } else {
            // Log.w(TAG, "Attempt to connect in state: " + connectionState);
            return false;
        }
    }

    /**
     * GATT client callbacks
     */
    private BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                            int newState) {
            if (gattMap.size() == 0) {
                // Log.e(TAG, "mBluetoothGatt not created!");
                return;
            }

            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            Log.e(TAG, "onConnectionStateChange (" + address + ") " + newState +
                    " status: " + status);

            try {
                switch (newState) {
                    case BluetoothProfile.STATE_CONNECTED:
                        //gattMap.put(address, gatt);
                        broadcastUpdate(ACTION_GATT_CONNECTED, address, status);
                        break;
                    case BluetoothProfile.STATE_DISCONNECTED:
                        /*BluetoothGatt b = gattMap.get(address);
                        if (b != null) {
                            b.close();
                        }*/
                        /*if (isDisconnectAll) {
                            if (gattMap.size() == disconnectNum) {
                                gattMap.clear();
                            }
                        } else {
                            gattMap.remove(address);
                        }*/
                        broadcastUpdate(ACTION_GATT_DISCONNECTED, address, status);
                        break;
                    default:
                        // Log.e(TAG, "New state not processed: " + newState);
                        break;
                }
            } catch (NullPointerException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            BluetoothDevice device = gatt.getDevice();
            broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED, device.getAddress(),
                    status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            broadcastUpdate(ACTION_DATA_NOTIFY, characteristic,
                    BluetoothGatt.GATT_SUCCESS, address);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic, int status) {
            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            broadcastUpdate(ACTION_DATA_READ, characteristic, status, address);
            if (characteristics != null) {
                doReadCharacteristics(address);
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt,
                                          BluetoothGattCharacteristic characteristic, int status) {
            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            broadcastUpdate(ACTION_DATA_WRITE, characteristic, status, address);
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt,
                                     BluetoothGattDescriptor descriptor, int status) {
            mBusy = false;
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt,
                                      BluetoothGattDescriptor descriptor, int status) {
            // Log.i(TAG, "onDescriptorWrite: " + descriptor.getUuid().toString());
            mBusy = false;
        }
    };

}
